package com.itic.appbase.framework.utils.WXPay;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;

import org.apache.commons.httpclient.util.HttpURLConnection;
import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.itic.appbase.framework.utils.NumberStringHelper;
import net.sf.json.JSONObject;
import javax.net.ssl.TrustManager;
public class WeixinUtil {


    public  static final String notifyUrl = "https://xcx.uhuijia.com.cn/hpp/hpp/wx/pay/success";
    public static WXPayResult  wxPay(String openId,String appId,String mchId,String AppSecret,String orderNo,double actualPay,String spbillCreateIp,String body) {
        //支付密钥
        String key = "&key="+AppSecret;
        //交易类型
        String tradeType = "JSAPI";
        //随机字符串
        String nonceStr = getNonceStr();
        //微信用户唯一id
        Map<String, String> sParaTemp = new HashMap<String, String>();
        sParaTemp.put("appid", appId);
        sParaTemp.put("attach", AppSecret);
        sParaTemp.put("body",  body);
        sParaTemp.put("mch_id", mchId);
        sParaTemp.put("nonce_str", nonceStr);
        sParaTemp.put("notify_url",notifyUrl);
        sParaTemp.put("openid", openId);
        sParaTemp.put("out_trade_no", orderNo);
        sParaTemp.put("spbill_create_ip", spbillCreateIp);
        //实际支付金额,已分为单位
       // actualPay = 0.01;
        String totalFee = NumberStringHelper.mulString(Double.toString(actualPay), "100", 0);
        sParaTemp.put("total_fee",totalFee);

        sParaTemp.put("trade_type", tradeType);
        //去掉空值 跟 签名参数(空值不参与签名，所以需要去掉)
        Map<String, String> map = paraFilter(sParaTemp);
        /**
         按照 参数=参数值&参数2=参数值2 这样的形式拼接（拼接需要按照ASCII码升序排列）
         */
        String mapStr = createLinkString(map);
        //MD5运算生成签名
        String sign =sign(mapStr, key, "utf-8").toUpperCase();
        sParaTemp.put("sign", sign);
        /**
         组装成xml参数,此处偷懒使用手动组装，严格代码可封装一个方法，XML标排序需要注意，ASCII码升序排列
         */
        String xml = "<xml>" + "<appid>" + appId + "</appid>"
                + "<attach>" + AppSecret + "</attach>"
                + "<body>" + body + "</body>"
                + "<mch_id>" + mchId + "</mch_id>"
                + "<nonce_str>" + nonceStr + "</nonce_str>"
                + "<notify_url>" + notifyUrl + "</notify_url>"
                + "<openid>" + openId + "</openid>"
                + "<out_trade_no>" + orderNo + "</out_trade_no>"
                + "<spbill_create_ip>" + spbillCreateIp + "</spbill_create_ip>"
                + "<total_fee>"+totalFee+"</total_fee>"
                + "<trade_type>" + tradeType + "</trade_type>"
                + "<sign>" + sign + "</sign>"
                + "</xml>";
        //统一下单url，生成预付id
        String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //请求数据
        String result = XCXHttpRequest(url, "POST", xml);
        WXPayResult  wxPayResult = parseWXPayResult(result,appId,key);
        return wxPayResult;

    }

    public static WXPayResult parseWXPayResult(String result,String appId,String key) {
        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
        //得到预支付id
        WXPayResult wxPayResult = getPayNo(result);
        if("SUCCESS".equals(wxPayResult.getError_code())) {
            String packages = "prepay_id="+wxPayResult.getPrepay_id();
            String nonceStr = getNonceStr();
            //开始第二次签名
            String mapStr1 = "appId="+appId+"&nonceStr=" + nonceStr + "&package=prepay_id=" + wxPayResult.getPrepay_id() + "&signType=MD5&timeStamp=" + timeStamp;
            String paySign = sign(mapStr1, key, "utf-8").toUpperCase();
            JSONObject obj = new JSONObject();
            obj.put("appId", appId);
            obj.put("timeStamp", timeStamp);
            obj.put("nonceStr", nonceStr);
            obj.put("package", packages);
            obj.put("signType", "MD5");
            obj.put("paySign", paySign);
            wxPayResult.setResult(obj);
        }
        return wxPayResult;
    }


    public static String getNonceStr() {
        //获得当前日期
        Date now = new Date();
        SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        String currTime = outFormat.format(now);
        //截取8位
        String strTime = currTime.substring(8, currTime.length());
        //得到4位随机整数
        int num = 1;
        double random = Math.random();
        if (random < 0.1) {
            random = random + 0.1;
        }
        for (int i = 0; i < 4; i++) {
            num = num * 10;
        }
        num = (int)random * num;
        return strTime + num;
    }

    /**
     * 除去数组中的空值和签名参数
     * @param sArray 签名参数组
     * @return 去掉空值与签名参数后的新签名参数组
     */
    public static Map<String, String> paraFilter(Map<String, String> sArray) {
        Map<String, String> result = new HashMap<String, String>();
        if (sArray == null || sArray.size() <= 0) {
            return result;
        }
        for (String key : sArray.keySet()) {
            String value = sArray.get(key);
            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
                    || key.equalsIgnoreCase("sign_type")) {
                continue;
            }
            result.put(key, value);
        }
        return result;
    }

    /**
     * 把数组所有元素排序，并按照“参数=参数值”的模式用“&”字符拼接成字符串
     * @param params 需要排序并参与字符拼接的参数组
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, String> params) {
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);

        String prestr = "";

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            if (i == keys.size() - 1) {// 拼接时，不包括最后一个&字符
                prestr = prestr + key + "=" + value;
            } else {
                prestr = prestr + key + "=" + value + "&";
            }
        }
        return prestr;
    }

    /**
     * MD5 加密，转为指定类型
     * @param text
     * @param key
     * @param input_charset
     * @return
     */
    public static String sign(String text, String key, String input_charset) {
        text = text + key;
        String sign = MD5Utils.MD5Encode(text, input_charset).toUpperCase();
        return sign;
    }

    public static byte[] getContentBytes(String content, String charset) {
        if (charset == null || "".equals(charset)) {
            return content.getBytes();
        }
        try {
            return content.getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
        }
    }
    /**
     * 解析微信放回的xml 为map
     * @param result
     * @return
     */
    public static Map<String, String> parseWXResult(String result){
        Map<String, String> map = new HashMap<String, String>();
        InputStream in = new ByteArrayInputStream(result.getBytes());
        SAXReader read = new SAXReader();
        Document doc =null;
        try {
            doc = read.read(in);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        //得到xml根元素
        Element root = doc.getRootElement();
        //遍历  得到根元素的所有子节点
        @SuppressWarnings("unchecked")
        List<Element> list =root.elements();
        for(Element element:list){
            //装进map
            map.put(element.getName(), element.getText());
        }
        return map;
    }

    /**
     * 解析xml得到 prepay_id 预支付id
     * @param result
     * @return
     * @throws DocumentException
     */
    public static WXPayResult getPayNo(String result){
        Map<String, String> map = parseWXResult(result);
        //返回码
        String return_code = map.get("return_code");
        //返回信息
        String result_code = map.get("result_code");
        //预支付id
        String prepay_id = "";
        //return_code 和result_code 都为SUCCESS 的时候返回 预支付id
        WXPayResult wxPayResult = new WXPayResult();
        if(return_code.equals("SUCCESS")&&result_code.equals("SUCCESS")){
            prepay_id = map.get("prepay_id");
            wxPayResult.setError_code("SUCCESS");
            wxPayResult.setPrepay_id(prepay_id);

        }else {
            wxPayResult.setError_code("FAIL");
            wxPayResult.setError_reson(result_code);
        }
        return wxPayResult;
    }

    /**
     * 小程序调用支付接口返回字符串数据
     * @param requestUrl
     * @param requestMethod
     * @param outputStr
     * @return
     */
    public static String XCXHttpRequest(String requestUrl, String requestMethod, String outputStr){
        JSONObject jsonObject = null;
        StringBuffer buffer = new StringBuffer();
        OutputStream outputStream =null;
        InputStream inputStream =null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        HttpsURLConnection httpUrlConn = null;
        try
        {
            TrustManager[] tm = {(TrustManager) new MyX509TrustManager()};
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, tm, new SecureRandom());
            SSLSocketFactory ssf = sslContext.getSocketFactory();
            URL url = new URL(requestUrl);
            httpUrlConn = (HttpsURLConnection)url.openConnection();
            httpUrlConn.setSSLSocketFactory(ssf);
            httpUrlConn.setDoOutput(true);
            httpUrlConn.setDoInput(true);
            httpUrlConn.setUseCaches(false);
            httpUrlConn.setRequestMethod(requestMethod);
            if ("GET".equalsIgnoreCase(requestMethod)) {
                httpUrlConn.connect();
            }
            if (outputStr != null) {
                outputStream = httpUrlConn.getOutputStream();
                outputStream.write(outputStr.getBytes("UTF-8"));
            }

            inputStream = httpUrlConn.getInputStream();
            inputStreamReader = new InputStreamReader(inputStream, "utf-8");
            bufferedReader = new BufferedReader(inputStreamReader);

            String str = null;
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                outputStream.close();
                bufferedReader.close();
                inputStreamReader.close();
                inputStream.close();
                httpUrlConn.disconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return buffer.toString();
    }

    /**
     * 解析 回调时的xml装进map 返回
     * @param result
     * @return
     * @throws DocumentException
     */
    public static Map<String, String> getNotifyUrl(String result) throws DocumentException{
        Map<String, String> map = new HashMap<String, String>();
        InputStream in = new ByteArrayInputStream(result.getBytes());
        SAXReader read = new SAXReader();
        Document doc = read.read(in);
        //得到xml根元素
        org.dom4j.Element root = doc.getRootElement();
        //遍历  得到根元素的所有子节点
        @SuppressWarnings("unchecked")
        List<Element> list =root.elements();
        for(Element element:list){
            //装进map
            map.put(element.getName().toString(), element.getText().toString());
        }
        return map;
    }

    /**
     * 验证签名，判断是否是从微信发过来
     * 验证方法：接收微信服务器回调我们url的时候传递的xml中的参数 然后再次加密，看是否与传递过来的sign签名相同
     * @param map
     * @return
     */
    public static boolean verifyWeixinNotify(Map<String, String> map,String key) {
        //根据微信服务端传来的各项参数 进行再一次加密后  与传过来的 sign 签名对比
        String mapStr = createLinkString(map);
        String signOwn = sign(mapStr, key, "utf-8").toUpperCase();         //根据微信端参数进行加密的签名
        String signWx = map.get("sign");                //微信端传过来的签名
        if(signOwn.equals(signWx)){
            //如果两个签名一致，验证成功
            return true;
        }
        return false;
    }

    /**
     * 将map转换成xml
     * @param parameters
     * @return
     * @throws Exception
     */
    @SuppressWarnings("rawtypes")
    public static String getFilterRequestXml(TreeMap<String, String> parameters)
            throws Exception {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            sb.append("<" + k + ">" + v + "</" + k + ">");
        }
        sb.append("</xml>");
        return sb.toString();
    }

    /**
     * 微信退款方式
     * @param appId
     * @param mchId
     * @param key
     * @param caLicense 证书地址
     * @param outRefundNo 退款编号
     * @param orderNum 订单号
     * @param refundFee 实际要退款金额
     * @param totalRefundFee 总的退款金额
     * @return
     * @throws Exception
     */
    public static String wxRefund(String appId,String mchId,String key,String caLicense,String outRefundNo,String orderNum,String refundFee,String totalRefundFee) throws Exception {
        String nonce_str1 =  StringUtils.getStrRandom(28);
        SortedMap<Object,Object> parameters1 = new TreeMap<Object,Object>();
        parameters1.put("appid", appId);
        parameters1.put("mch_id", mchId);
        parameters1.put("nonce_str", nonce_str1);
        parameters1.put("out_trade_no", orderNum);
        parameters1.put("out_refund_no", outRefundNo);
        refundFee = NumberStringHelper.mulString(refundFee, "100", 0);
        totalRefundFee = NumberStringHelper.mulString(totalRefundFee, "100", 0);
        parameters1.put("refund_fee", refundFee);
        parameters1.put("total_fee", totalRefundFee);
        String sign1 = SignUtils.creatSign("UTF-8", key,parameters1);
        //请求企业付款
        TreeMap<String, String> tmap1 = new TreeMap<String, String>();
        tmap1.put("appid", appId);
        tmap1.put("mch_id",mchId);
        tmap1.put("nonce_str", nonce_str1);
        tmap1.put("out_trade_no", orderNum);
        tmap1.put("out_refund_no", outRefundNo);
        tmap1.put("refund_fee", refundFee);
        tmap1.put("total_fee",totalRefundFee);
        tmap1.put("sign", sign1);
        String xml2 = getFilterRequestXml(tmap1);
        String  xml3= httpClientResultRefound(xml2,"UTF-8",mchId,caLicense);
        return xml3;
    }

    /**
     * httpClient 请求微信退款
     * @param xml
     * @param charset
     * @param mch_id
     * @param pkcdPath12
     * @return
     * @throws Exception
     */
    public static String httpClientResultRefound(String xml,String charset,String mch_id, String pkcdPath12) throws Exception{
        StringBuffer reultBuffer = new StringBuffer();
        SSLConnectionSocketFactory sslsf = ReadSSl.getInstance().readCustomSSL(mch_id,pkcdPath12);
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/secapi/pay/refund");
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
        StringEntity myEntity = new org.apache.http.entity.StringEntity(xml, charset);
        myEntity.setContentType("text/xml;charset=UTF-8");
        myEntity.setContentEncoding(charset);
        httpPost.setHeader("Content-Type", "text/xml; charset=UTF-8");
        httpPost.setEntity(myEntity);
        CloseableHttpResponse response      = null;
        InputStream inputStream		        = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader       = null;
        try {
            response = httpclient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            if (entity!=null){
                inputStream = entity.getContent();
                inputStreamReader = new InputStreamReader(inputStream, charset);
                bufferedReader = new BufferedReader(inputStreamReader);
                String str = null;
                while ((str = bufferedReader.readLine()) != null) {
                    reultBuffer.append(str);
                }
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            httpclient.close();
            response.close();
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
            inputStream = null;
        }
        return reultBuffer.toString();
    }

    /**
     * 解析微信退款返回结果
     * @param result
     * @return
     */
    public static WXRefundResult getWXRefundResult(String result) {
        Map<String, String> map = parseWXResult(result);
        //返回码
        String return_code = map.get("return_code");
        //返回信息
        String result_code = map.get("result_code");
        //预支付id
        String prepay_id = "";
        //return_code 和result_code 都为SUCCESS 的时候返回 预支付id
        WXRefundResult wxRefundResult = new WXRefundResult();
        if(return_code.equals("SUCCESS")&&result_code.equals("SUCCESS")){
            String transaction_id = map.get("transaction_id");
            String nonce_str = map.get("nonce_str");
            String out_refund_no = map.get("out_refund_no");
            String sign = map.get("sign");
            String return_msg = map.get("return_msg");
            String mch_id = map.get("mch_id");
            String refund_id = map.get("refund_id");
            String out_trade_no = map.get("out_trade_no");
            String appid = map.get("appid");
            String refund_fee = map.get("refund_fee");
            String total_fee = map.get("total_fee");
            wxRefundResult.setError_code("SUCCESS");
            wxRefundResult.setError_reson(return_msg);
            wxRefundResult.setAppId(appid);
            wxRefundResult.setMchId(mch_id);
            wxRefundResult.setOutRefundNo(out_refund_no);
            wxRefundResult.setOutTradeNo(out_trade_no);
            wxRefundResult.setRefundFee(refund_fee);
            wxRefundResult.setTotalRefundFee(total_fee);
            wxRefundResult.setTransactionId(transaction_id);

        }else {
            wxRefundResult.setError_code("FAIL");
            String err_code_des =map.get("err_code_des");
            String mch_id = map.get("mch_id");
            String appid = map.get("appid");
            wxRefundResult.setError_reson(err_code_des);
            wxRefundResult.setAppId(appid);
            wxRefundResult.setMchId(mch_id);
            wxRefundResult.setAppId(appid);
            wxRefundResult.setMchId(mch_id);
        }
        return wxRefundResult;
    }

    /**
     * 微信退款并解析返回请求结果
     * @param appId
     * @param mchId
     * @param key
     * @param caLicense
     * @param outRefundNo
     * @param orderNum
     * @param refundFee
     * @param totalRefundFee
     * @return
     * @throws Exception
     */
    public static WXRefundResult wxRefundResult(String appId,String mchId,String key,String caLicense,String outRefundNo,String orderNum,String refundFee,String totalRefundFee) throws Exception {
       String refundResult = wxRefund(appId,mchId,key,caLicense,outRefundNo,orderNum,refundFee,totalRefundFee);
        WXRefundResult wxRefundResult = getWXRefundResult(refundResult);
        return wxRefundResult;
    }
}
